package main

import (
	"encoding/json"
	"fmt"
	"github.com/syndtr/goleveldb/leveldb"
	"github.com/valyala/fasthttp"
	"io/ioutil"
	"path"
	"strings"
	"sync"
)

type Server struct {
	mu            sync.RWMutex
	authenticator Authenticator
	tables        map[string]map[string]Store
	dbtype        string
	basePath      string
	metric        *Metric
}

func (srv *Server) Setup() error {
	//search basepath for tableName
	srv.tables = make(map[string]map[string]Store)
	srv.metric = NewMetric()
	users, err := ioutil.ReadDir(srv.basePath)
	if err != nil {
		return err
	}
	for _, d := range users {
		if !d.IsDir() {
			continue
		}
		uname := d.Name()
		srv.tables[uname] = make(map[string]Store)
		tables, err := ioutil.ReadDir(path.Join(srv.basePath, uname))
		if err != nil {
			return err
		}
		for _, f := range tables {
			if f.IsDir() {
				tname := f.Name()
				db, err := NewStore(srv.basePath, uname, tname, srv.dbtype)
				if err != nil {
					return err
				}
				srv.tables[uname][tname] = db
			}
		}
	}
	return nil
}

func (srv *Server) Handle(ctx *fasthttp.RequestCtx) {
	//prmetheus export point
	if ctx.IsGet() && string(ctx.URI().Path()) == "/prome" {
		ctx.Success("text/plain; version=0.0.4", srv.metric.bytes())
		return
	}
	ret := NewResult()
	defer srv.metric.Add(ret)
	uname, ok := srv.auth(ctx)
	if !ok || uname == "" {
		ret.WithAction(AUTH).WithError(UnauthorizedError).WithCode(fasthttp.StatusUnauthorized)
	} else {
		table, k, v, batchData, action := parseArgs(ctx)
		switch action {
		case SHOW:
			t := srv.showTables(uname)
			ret.WithAction(SHOW).WithValue(t)
		case CREATE:
			ret.WithAction(CREATE)
			if err := srv.createTable(uname, table); err == nil {
				ret.WithValue(nil)
			} else {
				ret.WithError(err)
			}
		case GETBYPREFIX:
			ret.WithAction(GETBYPREFIX)
			if data, err := srv.scan(uname, table, k); err == nil {
				ret.WithValue(data)
			} else {
				ret.WithError(err)
			}
		case BATCH:
			ret.WithAction(BATCH)
			if err := srv.batchPut(uname, table, batchData); err == nil {
				ret.WithValue(v)
			} else {
				ret.WithError(err)
			}
		case GET:
			ret.WithAction(GET)
			if v, err := srv.get(uname, table, k); err == nil {
				ret.WithValue(v)
			} else if err == leveldb.ErrNotFound {
				ret.WithValue(nil).WithCode(fasthttp.StatusNotFound)
			} else {
				ret.WithError(err)
			}
		case PUT:
			ret.WithAction(PUT)
			if err := srv.put(uname, table, k, v); err == nil {
				ret.WithValue(nil)
			} else {
				ret.WithError(err)
			}
		default:
			ret.WithAction(NULL).WithError(ActionNotSupportError)
		}
	}
	if ret.code == 0 {
		if isParamError(ret.err) {
			ret.WithCode(fasthttp.StatusBadRequest)
		} else {
			ret.WithCode(fasthttp.StatusInternalServerError)
		}
	}
	if ret.code == fasthttp.StatusOK {
		ctx.Success("application/json", ret.Finish().bytes())
	} else {
		ctx.Error(ret.Finish().String(), ret.code)
	}
}

var TableNotExsitError = fmt.Errorf("TableNotExsit")
var ActionNotSupportError = fmt.Errorf("ActionNotSupport")
var TableExsitError = fmt.Errorf("TableExsit")
var UnauthorizedError = fmt.Errorf("UnauthorizedError")
var NullKeyOrValueError = fmt.Errorf("NullKeyOrValue")
var NullPrefixError = fmt.Errorf("NullPrefix")
var InValidBoltKeyError = fmt.Errorf("InValidBoltKey")


func isParamError(err error) bool {
	return err == TableNotExsitError || err == ActionNotSupportError ||
		err == TableExsitError || err == NullKeyOrValueError ||
		err == NullPrefixError
}

func (srv *Server) showTables(uname string) (ret []string) {
	srv.mu.RLock()
	defer srv.mu.RUnlock()
	for t := range srv.tables[uname] {
		ret = append(ret, t)
	}
	return
}

func (srv *Server) createTable(uname, tableName string) error {
	if srv.validateTable(uname, tableName) {
		return TableExsitError
	}
	srv.mu.Lock()
	defer srv.mu.Unlock()
	if _, ok := srv.tables[uname]; !ok {
		srv.tables[uname] = make(map[string]Store)
	}
	store, err := NewStore(srv.basePath, uname, tableName, srv.dbtype)
	if err != nil {
		return err
	}
	srv.tables[uname][tableName] = store
	return nil
}

func (srv *Server) scan(uname, tableName, prefix string) (map[string]string, error) {
	if prefix == "" {
		return nil, NullPrefixError
	}
	if !srv.validateTable(uname, tableName) {
		return nil, TableNotExsitError
	}
	srv.mu.Lock()
	defer srv.mu.Unlock()
	ret, err := srv.tables[uname][tableName].GetByPrefix(prefix)
	return ret, err
}
func (srv *Server) batchPut(uname, tableName string, data map[string]string) error {
	if len(data) == 0 {
		return NullKeyOrValueError
	}
	if !srv.validateTable(uname, tableName) {
		return TableNotExsitError
	}
	srv.mu.Lock()
	defer srv.mu.Unlock()
	return srv.tables[uname][tableName].BatchPut(data)
}
func (srv *Server) put(uname, tableName, key, value string) error {
	if value == "" || key == "" {
		return NullKeyOrValueError
	}
	if !srv.validateTable(uname, tableName) {
		return TableNotExsitError
	}
	srv.mu.Lock()
	defer srv.mu.Unlock()
	return srv.tables[uname][tableName].Put(key, value)
}

func (srv *Server) get(uname, tableName, key string) (string, error) {
	if key == "" {
		return "", NullKeyOrValueError
	}
	if !srv.validateTable(uname, tableName) {
		return "", TableNotExsitError
	}
	srv.mu.Lock()
	defer srv.mu.Unlock()
	v, err := srv.tables[uname][tableName].Get(key)
	return v, err
}

func (srv *Server) validateTable(uname, tableName string) bool {
	if tableName == "" {
		return false
	}
	srv.mu.RLock()
	defer srv.mu.RUnlock()
	_, ok1 := srv.tables[uname]
	if !ok1 {
		return false
	}
	_, ok := srv.tables[uname][tableName]
	return ok
}

func (srv *Server) auth(ctx *fasthttp.RequestCtx) (string, bool) {
	u, ok := srv.authenticator.Auth(ctx)
	return u, ok
}

func parseArgs(ctx *fasthttp.RequestCtx) (tableName, k, v string, batchdata map[string]string, action Action) {
	tableName = strings.TrimPrefix(string(ctx.URI().Path()), "/")
	if ctx.IsGet() {
		k = string(ctx.QueryArgs().Peek("key"))
		action = ParseAction(string(ctx.QueryArgs().Peek("action")))
		if action == GETBYPREFIX {
			k = string(ctx.QueryArgs().Peek("prefix"))
		}
	} else if ctx.IsPost() {
		k = string(ctx.PostArgs().Peek("key"))
		v = string(ctx.PostArgs().Peek("value"))
		action = ParseAction(string(ctx.PostArgs().Peek("action")))
		s := string(ctx.PostArgs().Peek("data"))
		json.Unmarshal([]byte(s), batchdata)
	}
	return
}
